//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/image/cpath.h>
#include <buola/image/cgc.h>
#include <buola/mat/c2dtransform.h>
#include <buola/mat/transformations.h>
#include <buola/image/cpolygon.h>

namespace buola { namespace img {

inline void CPath::NewSubPath()
{
    if(mSubPathLength)
    {
//        if(mFillIsRectilinear)
//        {
            //implicitly close
//            const CPoint_d &lLastMovePoint=LastMovePoint();
//            mFillIsRectilinear=mCurrentPoint.x==lLastMovePoint.x||mCurrentPoint.y==lLastMovePoint.y;
//            mFillMaybeRegion&=mFillIsRectilinear;
//        }
        mSubPathLength=0;
    }
}
    
inline void CPath::AddElement(EOperation pOp,const CPoint_d &pP)
{
    mExtents.AddPoint(mCurrentPoint);
    mElements.emplace_back(pOp,pP);
    mSubPathLength++;
}

inline void CPath::EnsureMoveTo()
{
    if(!mSubPathLength)
    {
        if(mCurrentPoint.Undefined()) 
            throw XInvalid("path must start with a MoveTo");
        AddElement(OP_MOVE,mCurrentPoint);
    }
}

void CPath::Reset()
{
    mCurrentPoint=nUndefined;
    mExtents=nUndefined;
    mSubPathLength=0;
    mElements.clear();
}

void CPath::MoveTo(const CPoint_d &pP)
{
    NewSubPath();
    mCurrentPoint=pP;
}

void CPath::LineTo(const CPoint_d &pP)
{
    EnsureMoveTo();

    if(pP==mCurrentPoint) return;

    AddElement(OP_LINE,pP);
    mCurrentPoint=pP;
}

void CPath::RelMoveTo(const CPoint_d &pP)
{
    if(mCurrentPoint.Undefined()) throw XInvalid("can't do relative move to without a current point");
    MoveTo(mCurrentPoint+pP);
}

void CPath::RelLineTo(const CPoint_d &pP)
{
    if(mCurrentPoint.Undefined()) throw XInvalid("can't do relative line to without a current point");
    LineTo(mCurrentPoint+pP);
}

void CPath::CurveTo(const CPoint_d &p1,const CPoint_d &p2,const CPoint_d &pE)
{
    EnsureMoveTo();

    if(p1==mCurrentPoint&&p2==mCurrentPoint&&pE==mCurrentPoint) return;

    AddElement(OP_CUBIC,p1);
    AddElement(OP_DATA,p2);
    AddElement(OP_DATA,pE);
    mCurrentPoint=pE;
}

void CPath::CurveTo(const CPoint_d &p1,const CPoint_d &pE)
{
    EnsureMoveTo();

    if(p1==mCurrentPoint&&pE==mCurrentPoint) return;

    AddElement(OP_CUBIC,p1);
    AddElement(OP_DATA,pE);
    mCurrentPoint=pE;
}

void CPath::Arc(const CPoint_d &pCenter,double pR,double pA1,double pA2)
{
    if(pR<=0)
    {
        if(mCurrentPoint.Undefined())
            MoveTo(pCenter);
        else
            LineTo(pCenter);
        return;
    }
    
    double lSin,lCos;
    sincos(pA1,&lSin,&lCos);
    CPoint_d l1(pCenter.x+pR*lCos,pCenter.y+pR*lSin);

    if(mCurrentPoint.Undefined())
        MoveTo(l1);
    else
        LineTo(l1);

    if(pA1==pA2) return;

    AddArc(pCenter,pR,pA1,pA2-pA1);
}

void CPath::RoundRect(const CRect_d &pR,double r)
{
    MoveTo(CPoint_d(pR.l+r,pR.t));
    LineTo(CPoint_d(pR.r-r,pR.t));
    Arc(CPoint_d(pR.r-r,pR.t+r),r,-M_PI/2,0);
    LineTo(CPoint_d(pR.r,pR.b-r));
    Arc(CPoint_d(pR.r-r,pR.b-r),r,0,M_PI/2);
    LineTo(CPoint_d(pR.l+r,pR.b));
    Arc(CPoint_d(pR.l+r,pR.b-r),r,M_PI/2,M_PI);
    LineTo(CPoint_d(pR.l,pR.t+r));
    Arc(CPoint_d(pR.l+r,pR.t+r),r,M_PI,3*M_PI/2);
}

void CPath::Rect(const CRect_d &pR)
{
    MoveTo(pR.TopLeft());
    LineTo(pR.TopRight());
    LineTo(pR.BottomRight());
    LineTo(pR.BottomLeft());
    Close();
}

void CPath::Close()
{
    if(!mSubPathLength) return;

    CPoint_d lLastMove=LastMove().mPoint;

    if(lLastMove!=mCurrentPoint)
    {
        //add a line to compute flags, then turn it into a close operation
        LineTo(lLastMove);
        mElements.back().mOp=OP_CLOSE;
    }

    mSubPathLength=0;
    mCurrentPoint=nUndefined;
}

void CPath::AddArc(const CPoint_d &pCenter,double pR,double pAngleStart,double pAngleSweep)
{
    static const double lMaxSweep=0.5*M_PI;
    pAngleStart=fmod(pAngleStart,2.0*M_PI);
    if(pAngleSweep>2.0*M_PI)
        pAngleSweep=2.0*M_PI;
    else if(pAngleSweep<-2.0*M_PI)
        pAngleSweep=-2.0*M_PI;
    else if(std::abs(pAngleSweep)<1e-10)
    {
        double lSin,lCos;
        sincos(pAngleStart+pAngleSweep,&lSin,&lCos);
        LineTo({pCenter.x+pR*lCos,pCenter.y+pR*lSin});
        return;
    }

    do
    {
        double lLocalSweep;
        if(pAngleSweep<0.0)
        {
            if(pAngleSweep>=-lMaxSweep)
            {
                lLocalSweep=pAngleSweep;
                pAngleSweep=0.0;
            }
            else
            {
                lLocalSweep=-lMaxSweep;
                pAngleSweep+=lMaxSweep;
            }
        }
        else
        {
            if(pAngleSweep<=lMaxSweep)
            {
                lLocalSweep=pAngleSweep;
                pAngleSweep=0.0;
            }
            else
            {
                lLocalSweep=lMaxSweep;
                pAngleSweep-=lMaxSweep;
            }
        }

        double lSin,lCos;
        sincos(lLocalSweep/2.0,&lSin,&lCos);

        double lTX=(1.0-lCos)*4.0/3.0;
        double lTY=lSin-lTX*lCos/lSin;

        CPoint_d lP1(lCos+lTX,-lTY);
        CPoint_d lP2(lCos+lTX,lTY);
        CPoint_d lP3(lCos,lSin);

        double lSinEnd,lCosEnd;
        sincos(pAngleStart+lLocalSweep/2.0,&lSinEnd,&lCosEnd);

        CurveTo({pCenter.x+pR*(lP1.x*lCosEnd-lP1.y*lSinEnd),pCenter.y+pR*(lP1.x*lSinEnd+lP1.y*lCosEnd)},
                {pCenter.x+pR*(lP2.x*lCosEnd-lP2.y*lSinEnd),pCenter.y+pR*(lP2.x*lSinEnd+lP2.y*lCosEnd)},
                {pCenter.x+pR*(lP3.x*lCosEnd-lP3.y*lSinEnd),pCenter.y+pR*(lP3.x*lSinEnd+lP3.y*lCosEnd)});
        
        pAngleStart+=lLocalSweep;
    } while(abs(pAngleSweep)>1e-10);
}

/*namespace img*/ } /*namespace buola*/ }
